使用缓存和重用之前的资源的能力,则是成为前端性能优化很关键的一个方面。
每个资源都可以通过 HTTP Cache-Control 头来定义相关的缓存策略,Cache-Control 可以控制谁,以及在什么条件下重用缓存,那么如何定义最佳的 Cache-Control 策略就显得格外重要。
Cache-control:
The Cache-Control general-header field is used to specify directives
that MUST be obeyed by all caching mechanisms along the
request/response chain. The directives specify behavior intended to
prevent caches from adversely interfering with the request or
response. These directives typically override the default caching
algorithms. Cache directives are unidirectional in that the presence
of a directive in a request does not imply that the same directive is
to be given in the response. –RFC2616
RFC 中定义的 Cache-Control 常用基本策略如下:
策略名称 | 属性说明 |
---|---|
no-cache | no-cache 表示客户端必须先和服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一网址的请求, 如果此时设置了 Etag 的话,no-cache 会发起往返通信来验证缓存的响应,如果确认资源未被更改,可以避免下载。 |
no-store | no-store 直接禁止浏览器以及所有中继缓存缓存返回的任何版本的响应,通过对一些隐私数据设置 no-store , 每次客户端请求该资源时,都会向服务器发送一个请求,并且每次都会下载完整的响应。 |
public | 如果响应被标记为 public,即使有关联的 HTTP 认证(401),甚至响应状态码无法正常缓存,响应也可以被缓存。 大多数情况下,public 不是必须的,因为明确的缓存信息(例如 max-age)已表示 响应可以被缓存。 |
private | 浏览器客户端可以缓存 private 响应,但通常旨为单个用户缓存,因此任何中继缓存都无法对其进行缓存,对于一些包含私密信息的 资源设置 private 可以防止 CDN 进行缓存 |
max-age | 定义从当前请求开始,允许资源被重用的最长时间,单位为秒 |
那么如何定义最佳的 Cache-Control 策略?
可以参照该决策树为相关资源定义缓存,客户端应该尽量缓存可以缓存的响应,并且缓存尽可能长的时间,并且为资源配置 Etag 高效的进行重新验证。
举例:
Cache-control 属性 | 属性说明 |
---|---|
max-age=86400 | 浏览器以及任何缓存中继都可以缓存 缓存时间为 86400 s,也就是 24h |
private,max-age=600 | 客户端浏览器只能将响应缓存最长 600 s |
no-store | 不允许缓存该资源,每次请求都需要获取完整响应 |
更新已缓存的资源
通过设置相关的缓存策略,浏览器发出的 HTTP 请求资源首先会被浏览器缓存,如果有匹配的响应,则在请求时直接从缓存中读取。但这样一来,如何更新已经缓存的资源又是一个挑战,假如服务端已经告诉浏览器某个文件资源缓存长达 24 小时,而此时开发者紧急提交了一个更新,该更新需要立即生效,如何让所有已经缓存过旧版本资源的用户下一次请求时能获取最新的资源,而不是使用缓存?
在文件资源内容发生更改的时候,我们可以通过更改资源的网址来强制用户下载新响应。一般的做法是给每一个文件资源分配一个指纹码(fingerprint)或者是版本号来,前端开发中具体可以通过相关的自动化工具实现(比如 Baidu Fis)
定义的缓存层级如下:
- HTML 文档资源被标记成了 no-cache,意味着每次浏览器请求时都会重新验证文档,如果内容发生更改,则获取最新版本与此同时,CSS 和 JavaScript 资源被嵌了指纹码,若这些文件资源内容发生更改,HTML 文档也会随之更改,并下载最新版本的资源
- 允许浏览器以及中继缓存(比如 CDN)缓存 CSS,max-age=31536000,因为已经为每个文件加入指纹码,那么可以放心的使用 max-age ,如果 CSS 资源更新,则相关得 URL 也会改变。
- JavaScript 缓存过期时间为 31536000,设置为 private,表示不允许其他中继进行缓存。
- 图片资源一般不添加指纹码,缓存时间为 24h
总之,Cache-Control 配合 Etag 以及利用文件 Fingerprint,便可对缓存进行高效的控制,并且实现按需更新。
其他技巧:
- 确保服务端配置了 Etag
- 确定中继缓存可以缓存哪些资源:有些资源可能不需要进行中继缓存
- 选择最优的资源缓存周期
- 确定资源最佳的缓存层级
- 分离频繁更新的资源,做到搅动最小化:对于更新比较频繁的资源,如果只有该资源的特定部分会经常更新,可以考虑将其单独拆分出来,作为单独的文件提供给客户端,其他部分则在缓存中获取,最小化再次请求时的下载量